using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;

namespace FinMkts.Messaging.Pgm
{
    /// <summary>
    /// Reliable multicast socket extensions
    /// </summary>
    public static class RmSocketExtensions
    {
        public static object GetSocketOption(this Socket socket, RmSocketOptionName option)
        {
            if (socket == null) throw new ArgumentNullException("socket");
            return socket.GetSocketOption(RmConstants.OptionLevel, (SocketOptionName)option);
        }

        public static void SetSocketOption(this Socket socket, RmSocketOptionName option, byte[] value)
        {
            if (socket == null) throw new ArgumentNullException("socket");
            if (value == null) throw new ArgumentNullException("value");
            socket.SetSocketOption(RmConstants.OptionLevel, (SocketOptionName)option, value);
        }

        public static void SetSocketOption(this Socket socket, RmSocketOptionName option, int value)
        {
            if (socket == null) throw new ArgumentNullException("socket");
            socket.SetSocketOption(RmConstants.OptionLevel, (SocketOptionName)option, value);
        }

        public static void SetSocketOption(this Socket socket, RmSocketOptionName option, bool value)
        {
            if (socket == null) throw new ArgumentNullException("socket");
            socket.SetSocketOption(RmConstants.OptionLevel, (SocketOptionName)option, value);
        }

        public static void SetSocketOption(this Socket socket, RmSocketOptionName option, IPAddress value)
        {
            socket.SetSocketOption(option, value.GetAddressBytes());
        }

        /// <summary>
        /// Sets the transmission rate (Kb/Sec) + window size (Kb and/or MSec)
        /// </summary>
        /// <remarks>Sender only</remarks>
        public static void SetSendWindow(this Socket socket, RmSendWindow window)
        {
            byte[] bytes = ConvertStructToBytes(window);
            socket.SetSocketOption(RmSocketOptionName.RateWindowSize, bytes);
        }

        static byte[] ConvertStructToBytes<T>(T obj)
        {
            var buffer = new byte[Marshal.SizeOf(obj)];
            var gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            try
            {
                Marshal.StructureToPtr(obj, gcHandle.AddrOfPinnedObject(), false);
            }
            finally
            {
                gcHandle.Free();
            }
            return buffer;
        }

        /// <summary>
        /// Get receiver statistics
        /// </summary>
        /// <remarks>Receiver only</remarks>
        public static RmReceiverStats GetReceiverStats(this Socket socket)
        {
            if (socket == null) throw new ArgumentNullException("socket");
            var buffer = new byte[Marshal.SizeOf(typeof(RmReceiverStats))];
            socket.GetSocketOption(RmConstants.OptionLevel, (SocketOptionName)RmSocketOptionName.ReceiverStatistics, buffer);
            var gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            try
            {
                return (RmReceiverStats) Marshal.PtrToStructure(gcHandle.AddrOfPinnedObject(), typeof(RmReceiverStats));
            }
            finally
            {
                gcHandle.Free();
            }
        }

        /// <summary>
        /// Gets the transmission rate (Kb/Sec) + window size (Kb and/or MSec)
        /// </summary>
        /// <remarks>Sender only</remarks>
        public static RmSendWindow GetSendWindow(this Socket socket)
        {
            if (socket == null) throw new ArgumentNullException("socket");
            var buffer = new byte[Marshal.SizeOf(typeof(RmSendWindow))];
            socket.GetSocketOption(RmConstants.OptionLevel, (SocketOptionName)RmSocketOptionName.RateWindowSize, buffer);
            var gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            try
            {
                return (RmSendWindow)Marshal.PtrToStructure(gcHandle.AddrOfPinnedObject(), typeof(RmSendWindow));
            }
            finally
            {
                gcHandle.Free();
            }
        }

        /// <summary>
        /// get sender statistics
        /// </summary>
        /// <remarks>Sender only</remarks>
        public static RmSenderStats GetSenderStats(this Socket socket)
        {
            if (socket == null) throw new ArgumentNullException("socket");
            var buffer = socket.GetSocketOption(RmConstants.OptionLevel, (SocketOptionName)RmSocketOptionName.SenderStatistics, Marshal.SizeOf(typeof(RmSenderStats)));
            var gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            try
            {
                return (RmSenderStats)Marshal.PtrToStructure(gcHandle.AddrOfPinnedObject(), typeof(RmSenderStats));
            }
            finally
            {
                gcHandle.Free();
            }
        }

    }
}